1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package org.codehaus.groovy.classgen.asm;
20  
21  import org.codehaus.groovy.GroovyBugError;
22  import org.codehaus.groovy.ast.ClassHelper;
23  import org.codehaus.groovy.ast.ClassNode;
24  import org.codehaus.groovy.ast.MethodNode;
25  import org.codehaus.groovy.ast.Parameter;
26  import org.codehaus.groovy.ast.Variable;
27  import org.codehaus.groovy.ast.VariableScope;
28  import org.objectweb.asm.Label;
29  import org.objectweb.asm.MethodVisitor;
30  import org.objectweb.asm.Opcodes;
31  
32  import java.util.*;
33  
34  /**
35   * This class is a helper for AsmClassGenerator. It manages
36   * different aspects of the code of a code block like
37   * handling labels, defining variables, and scopes.
38   * After a MethodNode is visited clear should be called, for
39   * initialization the method init should be used.
40   * <p>
41   * Some Notes:
42   * <ul>
43   * <li> every push method will require a later pop call
44   * <li> method parameters may define a category 2 variable, so
45   *      don't ignore the type stored in the variable object
46   * <li> the index of the variable may not be as assumed when
47   *      the variable is a parameter of a method because the
48   *      parameter may be used in a closure, so don't ignore
49   *      the stored variable index
50   * <li> the names of temporary variables can be ignored. The names
51   *      are only used for debugging and do not conflict with each
52   *      other or normal variables. For accessing, the index of the
53   *      variable must be used.
54   * <li> never mix temporary and normal variables by changes to this class.
55   *      While the name is very important for a normal variable, it is only a
56   *      helper construct for temporary variables. That means for example a
57   *      name for a temporary variable can be used multiple times without
58   *      conflict. So mixing them both may lead to the problem that a normal
59   *      or temporary variable is hidden or even removed.  That must not happen!
60   * </ul>
61   *
62   *
63   * @see org.codehaus.groovy.classgen.AsmClassGenerator
64   * @author Jochen Theodorou
65   */
66  public class CompileStack implements Opcodes {
67      /**
68       * @todo remove optimization of this.foo -> this.@foo
69       *
70       */
71  
72      // state flag
73      private boolean clear=true;
74      // current scope
75      private VariableScope scope;
76      // current label for continue
77      private Label continueLabel;
78      // current label for break
79      private Label breakLabel;
80      // available variables on stack
81      private Map stackVariables = new HashMap();
82      // index of the last variable on stack
83      private int currentVariableIndex = 1;
84      // index for the next variable on stack
85      private int nextVariableIndex = 1;
86      // currently temporary variables in use
87      private final LinkedList temporaryVariables = new LinkedList();
88      // overall used variables for a method/constructor
89      private final LinkedList usedVariables = new LinkedList();
90      // map containing named labels of parenting blocks
91      private Map superBlockNamedLabels = new HashMap();
92      // map containing named labels of current block
93      private Map currentBlockNamedLabels = new HashMap();
94      // list containing finally blocks
95      // such a block is created by synchronized or finally and
96      // must be called for break/continue/return
97      private LinkedList<BlockRecorder> finallyBlocks = new LinkedList<BlockRecorder>();
98      private LinkedList<BlockRecorder> visitedBlocks = new LinkedList<BlockRecorder>();
99  
100     private Label thisStartLabel, thisEndLabel;
101 
102 //    private MethodVisitor mv;
103 
104     // helper to handle different stack based variables
105     private final LinkedList stateStack = new LinkedList();
106 
107     // handle different states for the implicit "this"
108     private LinkedList<Boolean> implicitThisStack = new LinkedList();
109     // handle different states for being on the left hand side
110     private LinkedList<Boolean> lhsStack = new LinkedList();
111     {
112         implicitThisStack.add(false);
113         lhsStack.add(false);
114     }
115 
116     // defines the first variable index usable after
117     // all parameters of a method
118     private int localVariableOffset;
119     // this is used to store the goals for a "break foo" call
120     // in a loop where foo is a label.
121     private final Map namedLoopBreakLabel = new HashMap();
122     // this is used to store the goals for a "continue foo" call
123     // in a loop where foo is a label.
124     private final Map namedLoopContinueLabel = new HashMap();
125     private String className;
126     private LinkedList<ExceptionTableEntry> typedExceptions = new LinkedList<ExceptionTableEntry>();
127     private LinkedList<ExceptionTableEntry> untypedExceptions = new LinkedList<ExceptionTableEntry>();
128     // stores if on left-hand-side during compilation
129     private boolean lhs;
130     // stores if implicit or explicit this is used.
131     private boolean implicitThis;
132     private WriterController controller;
133     private boolean inSpecialConstructallCall;
134 
135     protected static class LabelRange {
136         public Label start;
137         public Label end;
138     }
139 
140     public static class BlockRecorder {
141         private boolean isEmpty = true;
142         public Runnable excludedStatement;
143         public LinkedList<LabelRange> ranges;
144         public BlockRecorder() {
145             ranges = new LinkedList<LabelRange>();
146         }
147         public BlockRecorder(Runnable excludedStatement) {
148             this();
149             this.excludedStatement = excludedStatement;
150         }
151         public void startRange(Label start) {
152             LabelRange range = new LabelRange();
153             range.start = start;
154             ranges.add(range);
155             isEmpty = false;
156         }
157         public void closeRange(Label end) {
158             ranges.getLast().end = end;
159         }
160     }
161 
162     private class ExceptionTableEntry {
163         Label start,end,goal;
164         String sig;
165     }
166 
167     private class StateStackElement {
168         final VariableScope scope;
169         final Label continueLabel;
170         final Label breakLabel;
171         final Map stackVariables;
172         final Map currentBlockNamedLabels;
173         final LinkedList<BlockRecorder> finallyBlocks;
174         final boolean inSpecialConstructallCall;
175 
176         StateStackElement() {
177             scope = CompileStack.this.scope;
178             continueLabel = CompileStack.this.continueLabel;
179             breakLabel = CompileStack.this.breakLabel;
180             stackVariables = CompileStack.this.stackVariables;
181             currentBlockNamedLabels = CompileStack.this.currentBlockNamedLabels;
182             finallyBlocks = CompileStack.this.finallyBlocks;
183             inSpecialConstructallCall = CompileStack.this.inSpecialConstructallCall;
184         }
185     }
186 
187     public CompileStack(WriterController wc) {
188         this.controller = wc;
189     }
190 
191     public void pushState() {
192         stateStack.add(new StateStackElement());
193         stackVariables = new HashMap(stackVariables);
194         finallyBlocks = new LinkedList(finallyBlocks);
195     }
196 
197     private void popState() {
198         if (stateStack.size()==0) {
199              throw new GroovyBugError("Tried to do a pop on the compile stack without push.");
200         }
201         StateStackElement element = (StateStackElement) stateStack.removeLast();
202         scope = element.scope;
203         continueLabel = element.continueLabel;
204         breakLabel = element.breakLabel;
205         stackVariables = element.stackVariables;
206         finallyBlocks = element.finallyBlocks;
207         inSpecialConstructallCall = element.inSpecialConstructallCall;
208     }
209 
210     public Label getContinueLabel() {
211         return continueLabel;
212     }
213 
214     public Label getBreakLabel() {
215         return breakLabel;
216     }
217 
218     public void removeVar(int tempIndex) {
219         final BytecodeVariable head = (BytecodeVariable) temporaryVariables.removeFirst();
220         if (head.getIndex() != tempIndex) {
221             temporaryVariables.addFirst(head);
222             MethodNode methodNode = controller.getMethodNode();
223             if (methodNode==null) {
224                 methodNode = controller.getConstructorNode();
225             }
226             throw new GroovyBugError(
227                     "In method "+ (methodNode!=null?methodNode.getText():"<unknown>") + ", " +
228                     "CompileStack#removeVar: tried to remove a temporary " +
229                     "variable with index "+ tempIndex + " in wrong order. " +
230                     "Current temporary variables=" + temporaryVariables);
231         }
232     }
233 
234     private void setEndLabels(){
235         Label endLabel = new Label();
236         controller.getMethodVisitor().visitLabel(endLabel);
237         for (Iterator iter = stackVariables.values().iterator(); iter.hasNext();) {
238             BytecodeVariable var = (BytecodeVariable) iter.next();
239             var.setEndLabel(endLabel);
240         }
241         thisEndLabel = endLabel;
242     }
243 
244     public void pop() {
245         setEndLabels();
246         popState();
247     }
248 
249     public VariableScope getScope() {
250         return scope;
251     }
252 
253     /**
254      * creates a temporary variable.
255      *
256      * @param var defines type and name
257      * @param store defines if the toplevel argument of the stack should be stored
258      * @return the index used for this temporary variable
259      */
260     public int defineTemporaryVariable(org.codehaus.groovy.ast.Variable var, boolean store) {
261         return defineTemporaryVariable(var.getName(), var.getType(),store);
262     }
263 
264     public BytecodeVariable getVariable(String variableName ) {
265         return getVariable(variableName, true);
266     }
267 
268     /**
269      * Returns a normal variable.
270      * <p>
271      * If <code>mustExist</code> is true and the normal variable doesn't exist,
272      * then this method will throw a GroovyBugError. It is not the intention of
273      * this method to let this happen! And the exception should not be used for
274      * flow control - it is just acting as an assertion. If the exception is thrown
275      * then it indicates a bug in the class using CompileStack.
276      * This method can also not be used to return a temporary variable.
277      * Temporary variables are not normal variables.
278      *
279      * @param variableName name of the variable
280      * @param mustExist    throw exception if variable does not exist
281      * @return the normal variable or null if not found (and <code>mustExist</code> not true)
282      */
283     public BytecodeVariable getVariable(String variableName, boolean mustExist) {
284         if (variableName.equals("this")) return BytecodeVariable.THIS_VARIABLE;
285         if (variableName.equals("super")) return BytecodeVariable.SUPER_VARIABLE;
286         BytecodeVariable v = (BytecodeVariable) stackVariables.get(variableName);
287         if (v == null && mustExist)
288             throw new GroovyBugError("tried to get a variable with the name " + variableName + " as stack variable, but a variable with this name was not created");
289         return v;
290     }
291 
292     /**
293      * creates a temporary variable.
294      *
295      * @param name defines type and name
296      * @param store defines if the top-level argument of the stack should be stored
297      * @return the index used for this temporary variable
298      */
299     public int defineTemporaryVariable(String name,boolean store) {
300         return defineTemporaryVariable(name, ClassHelper.DYNAMIC_TYPE,store);
301     }
302 
303     /**
304      * creates a temporary variable.
305      *
306      * @param name defines the name
307      * @param node defines the node
308      * @param store defines if the top-level argument of the stack should be stored
309      * @return the index used for this temporary variable
310      */
311     public int defineTemporaryVariable(String name, ClassNode node, boolean store) {
312         BytecodeVariable answer = defineVar(name, node, false, false);
313         temporaryVariables.addFirst(answer); // TRICK: we add at the beginning so when we find for remove or get we always have the last one
314         usedVariables.removeLast();
315 
316         if (store) controller.getOperandStack().storeVar(answer);
317 
318         return answer.getIndex();
319     }
320 
321     private void resetVariableIndex(boolean isStatic) {
322         temporaryVariables.clear();
323         if (!isStatic) {
324             currentVariableIndex=1;
325             nextVariableIndex=1;
326         } else {
327             currentVariableIndex=0;
328             nextVariableIndex=0;
329         }
330     }
331 
332     /**
333      * Clears the state of the class. This method should be called
334      * after a MethodNode is visited. Note that a call to init will
335      * fail if clear is not called before
336      */
337     public void clear() {
338         if (stateStack.size()>1) {
339             int size = stateStack.size()-1;
340             throw new GroovyBugError("the compile stack contains "+size+" more push instruction"+(size==1?"":"s")+" than pops.");
341         }
342         if (lhsStack.size()>1) {
343             int size = lhsStack.size()-1;
344             throw new GroovyBugError("lhs stack is supposed to be empty, but has " +
345                                      size + " elements left.");
346         }
347         if (implicitThisStack.size()>1) {
348             int size = implicitThisStack.size()-1;
349             throw new GroovyBugError("implicit 'this' stack is supposed to be empty, but has " +
350                                      size + " elements left.");
351         }
352         clear = true;
353         MethodVisitor mv = controller.getMethodVisitor();
354         // br experiment with local var table so debuggers can retrieve variable names
355         if (true) {//AsmClassGenerator.CREATE_DEBUG_INFO) {
356             if (thisEndLabel==null) setEndLabels();
357 
358             if (!scope.isInStaticContext()) {
359                 // write "this"
360                 mv.visitLocalVariable("this", className, null, thisStartLabel, thisEndLabel, 0);
361             }
362 
363             for (Iterator iterator = usedVariables.iterator(); iterator.hasNext();) {
364                 BytecodeVariable v = (BytecodeVariable) iterator.next();
365                 ClassNode t = v.getType();
366                 if (v.isHolder()) t = ClassHelper.REFERENCE_TYPE;
367                 String type = BytecodeHelper.getTypeDescription(t);
368                 Label start = v.getStartLabel();
369                 Label end = v.getEndLabel();
370                 mv.visitLocalVariable(v.getName(), type, null, start, end, v.getIndex());
371             }
372         }
373 
374         //exception table writing
375         for (ExceptionTableEntry ep : typedExceptions) {
376             mv.visitTryCatchBlock(ep.start, ep.end, ep.goal, ep.sig);
377         }
378         //exception table writing
379         for (ExceptionTableEntry ep : untypedExceptions) {
380             mv.visitTryCatchBlock(ep.start, ep.end, ep.goal, ep.sig);
381         }
382 
383 
384         pop();
385         typedExceptions.clear();
386         untypedExceptions.clear();
387         stackVariables.clear();
388         usedVariables.clear();
389         scope = null;
390         finallyBlocks.clear();
391         mv=null;
392         resetVariableIndex(false);
393         superBlockNamedLabels.clear();
394         currentBlockNamedLabels.clear();
395         namedLoopBreakLabel.clear();
396         namedLoopContinueLabel.clear();
397         continueLabel=null;
398         breakLabel=null;
399         thisStartLabel=null;
400         thisEndLabel=null;
401         mv = null;
402     }
403 
404     public void addExceptionBlock (Label start, Label end, Label goal,
405                                    String sig)
406     {
407         // this code is in an extra method to avoid
408         // lazy initialization issues
409         ExceptionTableEntry ep = new ExceptionTableEntry();
410         ep.start = start;
411         ep.end = end;
412         ep.sig = sig;
413         ep.goal = goal;
414         if (sig==null) {
415             untypedExceptions.add(ep);
416         } else {
417             typedExceptions.add(ep);
418         }
419     }
420 
421     /**
422      * initializes this class for a MethodNode. This method will
423      * automatically define variables for the method parameters
424      * and will create references if needed.  The created variables
425      * can be accessed by calling getVariable().
426      *
427      */
428     public void init(VariableScope el, Parameter[] parameters) {
429         if (!clear) throw new GroovyBugError("CompileStack#init called without calling clear before");
430         clear=false;
431         pushVariableScope(el);
432         defineMethodVariables(parameters,el.isInStaticContext());
433         this.className = BytecodeHelper.getTypeDescription(controller.getClassNode());
434     }
435 
436     /**
437      * Causes the state-stack to add an element and sets
438      * the given scope as new current variable scope. Creates
439      * a element for the state stack so pop has to be called later
440      */
441     public void pushVariableScope(VariableScope el) {
442         pushState();
443         scope = el;
444         superBlockNamedLabels = new HashMap(superBlockNamedLabels);
445         superBlockNamedLabels.putAll(currentBlockNamedLabels);
446         currentBlockNamedLabels = new HashMap();
447     }
448 
449     /**
450      * Should be called when descending into a loop that defines
451      * also a scope. Calls pushVariableScope and prepares labels
452      * for a loop structure. Creates a element for the state stack
453      * so pop has to be called later, TODO: @Deprecate
454      */
455     public void pushLoop(VariableScope el, String labelName) {
456         pushVariableScope(el);
457         continueLabel = new Label();
458         breakLabel = new Label();
459         if (labelName != null) {
460             initLoopLabels(labelName);
461         }
462     }
463 
464     /**
465      * Should be called when descending into a loop that defines
466      * also a scope. Calls pushVariableScope and prepares labels
467      * for a loop structure. Creates a element for the state stack
468      * so pop has to be called later
469      */
470     public void pushLoop(VariableScope el, List<String> labelNames) {
471         pushVariableScope(el);
472         continueLabel = new Label();
473         breakLabel = new Label();
474         if (labelNames != null) {
475             for (String labelName : labelNames) {
476                 initLoopLabels(labelName);
477             }
478         }
479     }
480 
481     private void initLoopLabels(String labelName) {
482         namedLoopBreakLabel.put(labelName,breakLabel);
483         namedLoopContinueLabel.put(labelName,continueLabel);
484     }
485 
486     /**
487      * Should be called when descending into a loop that does
488      * not define a scope. Creates a element for the state stack
489      * so pop has to be called later, TODO: @Deprecate
490      */
491     public void pushLoop(String labelName) {
492         pushState();
493         continueLabel = new Label();
494         breakLabel = new Label();
495         initLoopLabels(labelName);
496     }
497 
498     /**
499      * Should be called when descending into a loop that does
500      * not define a scope. Creates a element for the state stack
501      * so pop has to be called later
502      */
503     public void pushLoop(List<String> labelNames) {
504         pushState();
505         continueLabel = new Label();
506         breakLabel = new Label();
507         if (labelNames != null) {
508             for (String labelName : labelNames) {
509                 initLoopLabels(labelName);
510             }
511         }
512     }
513 
514     /**
515      * Used for <code>break foo</code> inside a loop to end the
516      * execution of the marked loop. This method will return the
517      * break label of the loop if there is one found for the name.
518      * If not, the current break label is returned.
519      */
520     public Label getNamedBreakLabel(String name) {
521         Label label = getBreakLabel();
522         Label endLabel = null;
523         if (name!=null) endLabel = (Label) namedLoopBreakLabel.get(name);
524         if (endLabel!=null) label = endLabel;
525         return label;
526     }
527 
528     /**
529      * Used for <code>continue foo</code> inside a loop to continue
530      * the execution of the marked loop. This method will return
531      * the break label of the loop if there is one found for the
532      * name. If not, getLabel is used.
533      */
534     public Label getNamedContinueLabel(String name) {
535         Label label = getLabel(name);
536         Label endLabel = null;
537         if (name!=null) endLabel = (Label) namedLoopContinueLabel.get(name);
538         if (endLabel!=null) label = endLabel;
539         return label;
540     }
541 
542     /**
543      * Creates a new break label and a element for the state stack
544      * so pop has to be called later
545      */
546     public Label pushSwitch(){
547         pushState();
548         breakLabel = new Label();
549         return breakLabel;
550     }
551 
552     /**
553      * because a boolean Expression may not be evaluated completely
554      * it is important to keep the registers clean
555      */
556     public void pushBooleanExpression(){
557         pushState();
558     }
559 
560     private BytecodeVariable defineVar(String name, ClassNode type, boolean holder, boolean useReferenceDirectly) {
561         int prevCurrent = currentVariableIndex;
562         makeNextVariableID(type,useReferenceDirectly);
563         int index = currentVariableIndex;
564         if (holder && !useReferenceDirectly) index = localVariableOffset++;
565         BytecodeVariable answer = new BytecodeVariable(index, type, name, prevCurrent);
566         usedVariables.add(answer);
567         answer.setHolder(holder);
568         return answer;
569     }
570 
571     private void makeLocalVariablesOffset(Parameter[] paras,boolean isInStaticContext) {
572         resetVariableIndex(isInStaticContext);
573 
574         for (int i = 0; i < paras.length; i++) {
575             makeNextVariableID(paras[i].getType(),false);
576         }
577         localVariableOffset = nextVariableIndex;
578 
579         resetVariableIndex(isInStaticContext);
580     }
581 
582     private void defineMethodVariables(Parameter[] paras, boolean isInStaticContext) {
583         Label startLabel  = new Label();
584         thisStartLabel = startLabel;
585         controller.getMethodVisitor().visitLabel(startLabel);
586 
587         makeLocalVariablesOffset(paras,isInStaticContext);
588 
589         for (int i = 0; i < paras.length; i++) {
590             String name = paras[i].getName();
591             BytecodeVariable answer;
592             ClassNode type = paras[i].getType();
593             if (paras[i].isClosureSharedVariable()) {
594                 boolean useExistingReference = paras[i].getNodeMetaData(ClosureWriter.UseExistingReference.class) != null;
595                 answer = defineVar(name, paras[i].getOriginType(), true, useExistingReference);
596                 answer.setStartLabel(startLabel);
597                 if (!useExistingReference) {
598                     controller.getOperandStack().load(type,currentVariableIndex);
599                     controller.getOperandStack().box();
600 
601                     // GROOVY-4237, the original variable should always appear
602                     // in the variable index, otherwise some programs get into
603                     // trouble. So we define a dummy variable for the packaging
604                     // phase and let it end right away before the normal
605                     // reference will be used
606                     Label newStart = new Label();
607                     controller.getMethodVisitor().visitLabel(newStart);
608                     BytecodeVariable var = new BytecodeVariable(currentVariableIndex, paras[i].getOriginType(), name, currentVariableIndex);
609                     var.setStartLabel(startLabel);
610                     var.setEndLabel(newStart);
611                     usedVariables.add(var);
612                     answer.setStartLabel(newStart);
613 
614                     createReference(answer);
615                 }
616             } else {
617                 answer = defineVar(name, type, false, false);
618                 answer.setStartLabel(startLabel);
619             }
620             stackVariables.put(name, answer);
621         }
622 
623         nextVariableIndex = localVariableOffset;
624     }
625 
626     private void createReference(BytecodeVariable reference) {
627         MethodVisitor mv = controller.getMethodVisitor();
628         mv.visitTypeInsn(NEW, "groovy/lang/Reference");
629         mv.visitInsn(DUP_X1);
630         mv.visitInsn(SWAP);
631         mv.visitMethodInsn(INVOKESPECIAL, "groovy/lang/Reference", "<init>", "(Ljava/lang/Object;)V", false);
632         mv.visitVarInsn(ASTORE, reference.getIndex());
633     }
634 
635     private void pushInitValue(ClassNode type, MethodVisitor mv) {
636         if (ClassHelper.isPrimitiveType(type)) {
637             if (type==ClassHelper.long_TYPE) {
638                 mv.visitInsn(LCONST_0);
639             } else if (type==ClassHelper.double_TYPE) {
640                 mv.visitInsn(DCONST_0);
641             } else if (type==ClassHelper.float_TYPE) {
642                 mv.visitInsn(FCONST_0);
643             } else {
644                 mv.visitLdcInsn(0);
645             }
646         } else {
647             mv.visitInsn(ACONST_NULL);
648         }
649     }
650 
651     /**
652      * Defines a new Variable using an AST variable.
653      * @param initFromStack if true the last element of the
654      *                      stack will be used to initialize
655      *                      the new variable. If false null
656      *                      will be used.
657      */
658     public BytecodeVariable defineVariable(Variable v, boolean initFromStack) {
659         return defineVariable(v, v.getOriginType(), initFromStack);
660     }
661     public BytecodeVariable defineVariable(Variable v, ClassNode variableType, boolean initFromStack) {
662         //TODO: any usage of this method should have different operand stack handing
663         //      then the remove(1) here and there in this one can be removed and others
664         //      can be changed
665         String name = v.getName();
666         BytecodeVariable answer = defineVar(name, variableType, v.isClosureSharedVariable(), v.isClosureSharedVariable());
667         stackVariables.put(name, answer);
668 
669         MethodVisitor mv = controller.getMethodVisitor();
670         Label startLabel  = new Label();
671         answer.setStartLabel(startLabel);
672         ClassNode type = answer.getType().redirect();
673         OperandStack operandStack = controller.getOperandStack();
674 
675         if (!initFromStack) pushInitValue(type, mv);
676         operandStack.push(answer.getType());
677         if (answer.isHolder())  {
678             operandStack.box();
679             operandStack.remove(1);
680             createReference(answer);
681         } else {
682             operandStack.storeVar(answer);
683         }
684 
685         mv.visitLabel(startLabel);
686         return answer;
687     }
688 
689     /**
690      * @param name the name of the variable of interest
691      * @return true if a variable is already defined
692      */
693     public boolean containsVariable(String name) {
694         return stackVariables.containsKey(name);
695     }
696 
697     /**
698      * Calculates the index of the next free register stores it
699      * and sets the current variable index to the old value
700      */
701     private void makeNextVariableID(ClassNode type, boolean useReferenceDirectly) {
702         currentVariableIndex = nextVariableIndex;
703         if ((type==ClassHelper.long_TYPE || type==ClassHelper.double_TYPE) && !useReferenceDirectly) {
704             nextVariableIndex++;
705         }
706         nextVariableIndex++;
707     }
708 
709     /**
710      * Returns the label for the given name
711      */
712     public Label getLabel(String name) {
713         if (name==null) return null;
714         Label l = (Label) superBlockNamedLabels.get(name);
715         if (l==null) l = createLocalLabel(name);
716         return l;
717     }
718 
719     /**
720      * creates a new named label
721      */
722     public Label createLocalLabel(String name) {
723         Label l = (Label) currentBlockNamedLabels.get(name);
724         if (l==null) {
725             l = new Label();
726             currentBlockNamedLabels.put(name,l);
727         }
728         return l;
729     }
730 
731     public void applyFinallyBlocks(Label label, boolean isBreakLabel) {
732         // first find the state defining the label. That is the state
733         // directly after the state not knowing this label. If no state
734         // in the list knows that label, then the defining state is the
735         // current state.
736         StateStackElement result = null;
737         for (ListIterator iter = stateStack.listIterator(stateStack.size()); iter.hasPrevious();) {
738             StateStackElement element = (StateStackElement) iter.previous();
739             if (!element.currentBlockNamedLabels.values().contains(label)) {
740                 if (isBreakLabel && element.breakLabel != label) {
741                     result = element;
742                     break;
743                 }
744                 if (!isBreakLabel && element.continueLabel != label) {
745                     result = element;
746                     break;
747                 }
748             }
749         }
750 
751         List<BlockRecorder> blocksToRemove;
752         if (result==null) {
753             // all Blocks do know the label, so use all finally blocks
754             blocksToRemove = (List<BlockRecorder>) Collections.EMPTY_LIST;
755         } else {
756             blocksToRemove = result.finallyBlocks;
757         }
758 
759         List<BlockRecorder> blocks = new LinkedList<BlockRecorder>(finallyBlocks);
760         blocks.removeAll(blocksToRemove);
761         applyBlockRecorder(blocks);
762     }
763 
764     private void applyBlockRecorder(List<BlockRecorder> blocks) {
765         if (blocks.size()==0 || blocks.size()==visitedBlocks.size()) return;
766 
767         MethodVisitor mv = controller.getMethodVisitor();
768 
769         Label end = new Label();
770         mv.visitInsn(NOP);
771         mv.visitLabel(end);
772         Label newStart = new Label();
773 
774         for (BlockRecorder fb : blocks) {
775             if (visitedBlocks.contains(fb)) continue;
776 
777             fb.closeRange(end);
778 
779             // we exclude the finally block from the exception table
780             // here to avoid double visiting of finally statements
781             fb.excludedStatement.run();
782 
783             fb.startRange(newStart);
784         }
785 
786         mv.visitInsn(NOP);
787         mv.visitLabel(newStart);
788     }
789 
790     public void applyBlockRecorder() {
791         applyBlockRecorder(finallyBlocks);
792     }
793 
794     public boolean hasBlockRecorder() {
795         return !finallyBlocks.isEmpty();
796     }
797 
798     public void pushBlockRecorder(BlockRecorder recorder) {
799         pushState();
800         finallyBlocks.addFirst(recorder);
801     }
802 
803     public void pushBlockRecorderVisit(BlockRecorder finallyBlock) {
804         visitedBlocks.add(finallyBlock);
805     }
806 
807     public void popBlockRecorderVisit(BlockRecorder finallyBlock) {
808         visitedBlocks.remove(finallyBlock);
809     }
810 
811     public void writeExceptionTable(BlockRecorder block, Label goal, String sig) {
812         if (block.isEmpty) return;
813         MethodVisitor mv = controller.getMethodVisitor();
814         for (LabelRange range : block.ranges) {
815             mv.visitTryCatchBlock(range.start, range.end, goal, sig);
816         }
817     }
818 
819 //    public MethodVisitor getMethodVisitor() {
820 //        return mv;
821 //    }
822 
823     public boolean isLHS() {
824         return lhs;
825     }
826 
827     public void pushLHS(boolean lhs) {
828         lhsStack.add(lhs);
829         this.lhs = lhs;
830     }
831 
832     public void popLHS() {
833         lhsStack.removeLast();
834         this.lhs = lhsStack.getLast();
835     }
836 
837     public void pushImplicitThis(boolean implicitThis) {
838         implicitThisStack.add(implicitThis);
839         this.implicitThis = implicitThis;
840     }
841 
842     public boolean isImplicitThis() {
843         return implicitThis;
844     }
845 
846     public void popImplicitThis() {
847         implicitThisStack.removeLast();
848         this.implicitThis = implicitThisStack.getLast();
849     }
850 
851     public boolean isInSpecialConstructorCall() {
852         return inSpecialConstructallCall;
853     }
854 
855     public void pushInSpecialConstructorCall() {
856         pushState();
857         inSpecialConstructallCall = true;
858     }
859 }